#!/usr/bin/perl -w
#
# $Id: GKismetApplication.pm,v 1.70 2003/08/01 01:37:00 solovam Exp $
#
# This file is a part of gkismet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#

#
# GKismetApplication class:
#
package GKismetApplication;

use Gnome;
use PacketBox;
use NetworkTree;
use StatusBar;
use Connection;
use ConnectionDialog;
use PrefsDialog;
use AboutDialog;
use Preferences;
use PacketWindow;
use StringWindow;
use PowerBox;
use GpsBox;
use Locator;
use RateGraph;
use BoomBox;
use Misc;
use strict;

#
# Constructor
#
sub new
{
	my $type = shift;
	my $self = {};
	bless $self, $type;

	# Paths
	my $prefix = shift;
	$self->{'path'}{'xpm'} = $prefix . '/share/gkismet/xpm';
	$self->{'path'}{'sound'} = $prefix . '/share/gkismet/sound';
	$self->{'path'}{'etc'} = '/etc/gkismet';

	# Gnome init
	init Gnome 'gkismet';

	# Create app
	my $application = new Gnome::App("gkismet", "gkismet");
	$self->{'application'} = $application;
	$application->set_default_size(Gtk::Gdk->screen_width() * 0.6, Gtk::Gdk->screen_height() * 0.8);
	$application->signal_connect("delete_event", sub {Gtk->exit(0)});

	# Preferences
	my $preferences = new Preferences($self);
	$self->{'preferences'} = $preferences;
	
	# Status line
	my $statusBar = new StatusBar($self);
	$self->{'persistentObservers'}{'statusBar'} = $statusBar;

	# My info box
	my $packetBox = new PacketBox($self);
	$self->{'persistentObservers'}{'packetBox'} = $packetBox;

	# My power box
	my $powerBox = new PowerBox($self);
	$self->{'persistentObservers'}{'powerBox'} = $powerBox;

	# My GPS box
	my $gpsBox = new GpsBox($self);
	$self->{'persistentObservers'}{'gpsBox'} = $gpsBox;

	# Ntree
	my $networkTree = new NetworkTree($self);
	$self->{'persistentObservers'}{'networkTree'} = $networkTree;
	$networkTree->addObserver($self);

	my $boomBox = new BoomBox($self);
	$self->{'persistentObservers'}{'boomBox'} = $boomBox;

	# Build menu info
	my @menuInfo =
	(
	{
		type => 'subtree', label => '_File', subtree =>
		[
			{type => 'item', label => '_Connect', pixmap_type => 'stock', pixmap_info => 'Menu_Open',
				callback => sub {$self->connect()}},
			{type => 'item', label => '_Disconnect', pixmap_type => 'stock', pixmap_info => 'Menu_Close',
				callback => sub {$self->disconnect()}},
			{type => 'separator'},
			{type => 'item', label => '_Quit', pixmap_type => 'stock', pixmap_info => 'Menu_Quit',
				callback => sub {Gtk->exit(0)}}
		]
	},
	{
		type => 'subtree', label => '_View', subtree =>
		[
			{type => 'item', label => '_Expand', pixmap_type => 'stock', pixmap_info => 'Menu_Book Open',
				callback => sub {$self->{'persistentObservers'}{'networkTree'}->expand()}},
			{type => 'item', label => '_Collapse', pixmap_type => 'stock', pixmap_info => 'Menu_Book Red',
				callback => sub {$self->{'persistentObservers'}{'networkTree'}->collapse()}},
			{type => 'item', label => '_Packet Dump', pixmap_type => 'stock', pixmap_info => 'Menu_Timer',
				callback => sub {$self->bssObserverOpenSelected('PacketWindow')}},
			{type => 'item', label => '_String Dump', pixmap_type => 'stock', pixmap_info => 'Menu_Timer',
				callback => sub {$self->bssObserverOpenSelected('StringWindow')}},
			{type => 'item', label => '_Locate', pixmap_type => 'stock', pixmap_info => 'Menu_Search',
				callback => sub {$self->bssObserverOpenSelected('Locator')}},
			{type => 'item', label => 'Packet _rate', pixmap_type => 'stock', pixmap_info => 'Menu_Timer',
				callback => sub {$self->transObserverOpen('RateGraph')}},
			{type => 'subtree', label => 'Sort', subtree =>
				[
					{type => 'item', label => 'Ascending',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setSortOrder('ascending')}},
					{type => 'item', label => 'Descending',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setSortOrder('descending')}},
				]
			},
			{type => 'subtree', label => 'Sort Networks', subtree =>
				[
					{type => 'item', label => 'By channel',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('channel')}},
					{type => 'item', label => 'By first seen',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('firsttime')}},
					{type => 'item', label => 'By last seen',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('lasttime')}},
					{type => 'item', label => 'By BSSID',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('bssid')}},
					{type => 'item', label => 'By SSID',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('ssid')}},
					{type => 'item', label => 'By data packet count',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('datapackets')}},
					{type => 'item', label => 'By signal power',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('signal')}},
					{type => 'item', label => 'By WEP',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setNetworksSortBy('wep')}},
				]
			},
			{type => 'subtree', label => 'Sort Clients', subtree =>
				[
					{type => 'item', label => 'By first seen',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setClientsSortBy('firsttime')}},
					{type => 'item', label => 'By last seen',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setClientsSortBy('lasttime')}},
					{type => 'item', label => 'By MAC',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setClientsSortBy('mac')}},
					{type => 'item', label => 'By data packet count',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setClientsSortBy('datapackets')}},
					{type => 'item', label => 'By signal power',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setClientsSortBy('signal')}},
					{type => 'item', label => 'By WEP',
						callback => sub {$self->{'persistentObservers'}{'networkTree'}->setClientsSortBy('wep')}},
				]
			}
		]
	},
	{
		type => 'subtree', label => '_Settings', subtree =>
		[
			{type => 'item', label => '_Preferences', pixmap_type => 'stock', pixmap_info => 'Menu_Preferences',
				callback => sub{$self->dialogOpen('PrefsDialog')}}
		]
	},
	{
		type => 'subtree', label => '_Help', subtree =>
		[
			{type => 'item', label => '_About', pixmap_type => 'stock', pixmap_info => 'Menu_About',
				callback => sub{$self->dialogOpen('AboutDialog')}}
		]
	}
	);

	# Build toolbar info
	my @toolbarInfo = (
		{type => 'item', label => 'Connect', pixmap_type => 'stock', pixmap_info => 'Open', 'hint' => 'Connect to Kismet server',
			callback => sub {$self->connect()}},
		{type => 'item', label => 'Disconnect', pixmap_type => 'stock', pixmap_info => 'Close', 'hint' => 'Disconnect from Kismet server',
			callback => sub {$self->disconnect()}},
		{type => 'item', label => 'Expand', pixmap_type => 'stock', pixmap_info => 'Book Open', 'hint' => 'Expand network node',
			callback => sub {$self->{'persistentObservers'}{'networkTree'}->expand()}},
		{type => 'item', label => 'Collapse', pixmap_type => 'stock', pixmap_info => 'Book Red', 'hint' => 'Collapse network node',
			callback => sub {$self->{'persistentObservers'}{'networkTree'}->collapse()}},
		{type => 'item', label => 'Packet Dump', pixmap_type => 'stock', pixmap_info => 'Timer', 'hint' => 'Open packet dump window',
			callback => sub {$self->bssObserverOpenSelected('PacketWindow')}},
		{type => 'item', label => 'String Dump', pixmap_type => 'stock', pixmap_info => 'Timer', 'hint' => 'Open string dump window',
			callback => sub {$self->bssObserverOpenSelected('StringWindow')}},
		{type => 'item', label => 'Locate', pixmap_type => 'stock', pixmap_info => 'Search', 'hint' => 'Open locator window',
			callback => sub {$self->bssObserverOpenSelected('Locator')}},
		{type => 'item', label => 'Packet rate', pixmap_type => 'stock', pixmap_info => 'Timer', 'hint' => 'Open packet rate window',
			callback => sub {$self->transObserverOpen('RateGraph')}},
	);

	# Pack widgets
	my $scrolledWindow = new Gtk::ScrolledWindow(undef, undef);
	$scrolledWindow->set_policy('automatic', 'automatic');
	my $vbox = new Gtk::VBox($false, 0);
	$vbox->pack_start($packetBox->getWidget(), $false, $false, 10);
	$vbox->pack_start($gpsBox->getWidget(), $false, $false, 10);
	$vbox->pack_start($powerBox->getWidget(), $true, $true, 10);
	my $hbox = new Gtk::HBox($false, 0);
	$hbox->pack_start($scrolledWindow, $true, $true, 0);
	$hbox->pack_end($vbox, $false, $false, 10);
	$scrolledWindow->add($networkTree->getWidget());

	# Assemble main window
	$application->create_menus(@menuInfo);
	$application->create_toolbar(@toolbarInfo);
	$application->set_statusbar($statusBar->getWidget());
	$application->set_contents($hbox);
	$application->show_all();

	# Items become active when a network is selected
	$self->{'selectedActItems'}{'expandMenuItem'} = $self->findWidgetInMenu(\@menuInfo, '_Expand'); 
	$self->{'selectedActItems'}{'collapseMenuItem'} = $self->findWidgetInMenu(\@menuInfo, '_Collapse');
	$self->{'selectedActItems'}{'packetDumpMenuItem'} = $self->findWidgetInMenu(\@menuInfo, '_Packet Dump');
	$self->{'selectedActItems'}{'stringDumpMenuItem'} = $self->findWidgetInMenu(\@menuInfo, '_String Dump');
	$self->{'selectedActItems'}{'locatorMenuItem'} = $self->findWidgetInMenu(\@menuInfo, '_Locate');
	$self->{'selectedActItems'}{'expandToolbarItem'} = $self->findWidgetInMenu(\@toolbarInfo, 'Expand');
	$self->{'selectedActItems'}{'collapseToolbarItem'} = $self->findWidgetInMenu(\@toolbarInfo, 'Collapse');
	$self->{'selectedActItems'}{'packetDumpToolbarItem'} = $self->findWidgetInMenu(\@toolbarInfo, 'Packet Dump');
	$self->{'selectedActItems'}{'stringDumpToolbarItem'} = $self->findWidgetInMenu(\@toolbarInfo, 'String Dump');
	$self->{'selectedActItems'}{'locatorToolbarItem'} = $self->findWidgetInMenu(\@toolbarInfo, 'Locate');
	for my $item (values %{$self->{'selectedActItems'}})
	{
		$item->set_sensitive($false);
	}

	# Items become active when a connection is established
	$self->{'connectedActItems'}{'rateMenuItem'} = $self->findWidgetInMenu(\@menuInfo, 'Packet _rate');
	$self->{'connectedActItems'}{'rateToolbarItem'} = $self->findWidgetInMenu(\@toolbarInfo, 'Packet rate');
	for my $item (values %{$self->{'connectedActItems'}})
	{
		$item->set_sensitive($false);
	}

	return $self;
}

#
# Find widget with specific label
#
sub findWidgetInMenu
{
	my $self = shift;
	my $menu = shift;
	my $label = shift;

	for my $item (@{$menu})
	{
		my $widget = $self->findWidget($item, $label);
		if(defined $widget)
		{
			return $widget;
		}
	}
	return undef;
}

#
# Find widget for a menu/toolbar item. Plucked from Gnome-Perl samples
#
sub findWidget
{
	my $self = shift;
	my $item = shift;
	my $label = shift;
	my $subtree;

	# Array
	if(ref($item) eq 'ARRAY')
	{
		# If found, return
		if($item->[0] eq 'item' && $item->[1] eq $label)
		{
			return $item->[-1];
		}

		# Is there a subtree?
		if($item->[0] eq 'subtree')
		{
			$subtree = $item->[3];
		}
	}
	# Hash
	else
	{
		# If found, return
		if($item->{'type'} eq 'item' && $item->{'label'} eq $label)
		{
			return $item->{'widget'};
		}

		# Is there a subtree?
		if ($item->{type} eq 'radioitems')
		{
                        $subtree = $item->{'moreinfo'};
                }
		if($item->{'type'} eq 'subtree')
		{
			$subtree = $item->{'subtree'};
		}
	}

	# If there was a subtree
	if(defined $subtree)
	{
		for (@{$subtree})
		{
			my $widget = $self->findWidget($_, $label);
			if(defined $widget)
			{
				return $widget;
			}
		}
        }

	# Not found
	return undef;
}

#
# Connect to server
#
sub connect
{
	my $self = shift;
	my $host = shift;
	my $port = shift;

	if(defined $self->{'connection'})
	{
		$self->disconnect();
	}

	if(!defined $host || !defined $port)
	{
		my $connectionDialog = new ConnectionDialog($self);
		($host, $port) = $connectionDialog->runAndClose();
		if(!defined $host)
		{
			return(0);
		}
	}

	my $connection = new Connection($self, $host, $port);
	if(!defined $connection)
	{
		my $messagebox = new Gnome::MessageBox("Can't connect to kismet server at $host:$port", 'error', 'Button_Ok');
		$messagebox->run_and_close();
		return(0);
	}
	$self->{'connection'} = $connection;

	$connection->addObserver($self);

	for my $item (values %{$self->{'connectedActItems'}})
	{
		$item->set_sensitive($true);
	}

	for my $observer (values %{$self->{'persistentObservers'}})
	{
		$observer->startObserving($connection);
	}

	return(1);
}

#
# Disconnect
#
sub disconnect
{
	my $self = shift;

	if(!defined $self->{'connection'})
	{
		return;
	}

	for my $observer (values %{$self->{'persistentObservers'}})
	{
		$observer->stopObserving();
	}
	for my $type (keys %{$self->{'transientObservers'}})
	{
		$self->transObserverClose($type);
	}
	for my $type (keys %{$self->{'bssObservers'}})
	{
		for my $bssid (keys %{$self->{'bssObservers'}{$type}})
		{
			$self->bssObserverClose($type, $bssid);
		}
	}

	for my $item (values %{$self->{'connectedActItems'}})
	{
		$item->set_sensitive($false);
	}

	$self->{'connection'}->removeObserver($self);

	$self->{'connection'}->disconnect();

	delete $self->{'connection'};
}

#
# Handle server quit
#
sub update
{
	my $self = shift;
	my $object = shift;
	my $data = shift;

	if($object->isa('Connection') && defined $self->{'connection'} && $self->{'connection'} eq $object)
	{
		if($data->{'changed'} eq 'disconnected')
		{
			my $messagebox = new Gnome::MessageBox("Lost connection to kismet server at " .
				$self->{'connection'}->host() . ":" . $self->{'connection'}->port(), 'error', 'Button_Ok');
			$messagebox->run_and_close();

			$self->disconnect();
		}
	}
	elsif($object->isa('NetworkTree') && $self->{'persistentObservers'}{'networkTree'} eq $object)
	{
		if($data->{'changed'} eq 'selection')
		{
			if($self->{'persistentObservers'}{'networkTree'}->getSelectedNetworks())
			{
				for my $item (values %{$self->{'selectedActItems'}})
				{
					$item->set_sensitive($true);
				}
			}
			else
			{
				for my $item (values %{$self->{'selectedActItems'}})
				{
					$item->set_sensitive($false);
				}
			}
		}
	}
}

#
# Run the app
#
sub run
{
	my $self = shift;

	my ($progname) = ($0 =~ /([^\/]*)$/);
	my $usage = "$progname <host> [port]\n";

	if($#ARGV > 1)
	{
		print $usage;
		exit 1;
	}

	my $host = $ARGV[0];
	my $port = $ARGV[1] ? $ARGV[1] : 2501;

	$self->connect($host, $port);

	main Gtk;

	exit(0);
}

#
# Get widget
#
sub getWidget
{
	my $self = shift;
	return $self->{'application'};
}

#
# Show a dialog
#
sub dialogOpen
{
	my $self = shift;
	my $type = shift;
	$type = lcfirst($type);
	if(!defined $self->{'dialogs'}{$type})
	{
		my $dialog = ucfirst($type)->new($self);
		$self->{'dialogs'}{$type} = $dialog;
	}
	else
	{
		$self->{'dialogs'}{$type}->getWidget()->window()->raise();
	}
}

#
# Close dialog
#
sub dialogClose
{
	my $self = shift;
	my $type = shift;
	$type = lcfirst($type);
	delete $self->{'dialogs'}{$type};
}

#
# Return number of observers by type
#
sub countObservers
{
	my $self = shift;
	my $type = shift;
	$type = lcfirst($type);
	return scalar keys %{$self->{'bssObservers'}{$type}};
}

#
# Open observers for selected networks
#
sub bssObserverOpenSelected
{
	my $self = shift;
	my $type = shift;
	my @selected = $self->{'persistentObservers'}{'networkTree'}->getSelectedNetworks();
	for my $bssid (@selected)
	{
		$self->bssObserverOpen($type, $bssid);
	}
}

#
# Open an observer for bssid
#
sub bssObserverOpen
{
	my $self = shift;
	my $type = shift;
	my $bssid = shift;

	$type = lcfirst($type);
	if(!defined $self->{'bssObservers'}{$type}{$bssid})
	{
		$self->{'bssObservers'}{$type}{$bssid} = ucfirst($type)->new($self, $self->{'connection'}, $bssid);
	}
	else
	{
		$self->{'bssObservers'}{$type}{$bssid}->getWidget()->window()->raise();
	}
}

#
# Close observers for selected networks
#
sub bssObserverCloseSelected
{
	my $self = shift;
	my $type = shift;
	my @selected = $self->{'persistentObservers'}{'networkTree'}->getSelectedNetworks();
	for my $bssid (@selected)
	{
		$self->bssObserverClose($type, $bssid);
	}
}

#
# Close an observer for bssid
#
sub bssObserverClose
{
	my $self = shift;
	my $type = shift;
	my $bssid = shift;
	$type = lcfirst($type);
	if(defined $self->{'bssObservers'}{$type}{$bssid})
	{
		$self->{'bssObservers'}{$type}{$bssid}->close();
		delete $self->{'bssObservers'}{$type}{$bssid};
	}
}

#
# Open observer
#
sub transObserverOpen
{	
	my $self = shift;
	my $type = shift;

	if(!defined $self->{'connection'})
	{
		return;
	}

	$type = lcfirst($type);
	if(!defined $self->{'transientObservers'}{$type})
	{
		$self->{'transientObservers'}{$type} = ucfirst($type)->new($self, $self->{'connection'});
	}
	else
	{
		$self->{'transientObservers'}{$type}->getWidget()->window()->raise();
	}
}

#
# Close observer
#
sub transObserverClose
{
	my $self = shift;
	my $type = shift;
	$type = lcfirst($type);
	if(defined $self->{'transientObservers'}{$type})
	{
		$self->{'transientObservers'}{$type}->close();
		delete $self->{'transientObservers'}{$type};
	}
}

1;
